package scoutobahn.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import scoutobahn.highway.GeoDistanceCalculator;
import scoutobahn.highway.HighwayGraph;
import scoutobahn.highway.factory.HighwayGraphFactory;
import scoutobahn.xml.automaton.OsmItemStreamManager;
import scoutobahn.xml.automaton.OsmSaxHandler;
/**
 * Class that builds the a graph network from an xml data source.
 * @author mark and florian koch
 *
 */
public class HighwayGraphBuilder implements OsmItemStreamManager,
    OsmItemProvider {
    private static final Long GERMANHIGHWAYSRELATIONID = new Long(20614);
    /**
     * Map of all nodes.
     */
    private final HashMap<Long, OsmNode> nodes;
    /**
     * Map of all ways.
     */
    private final HashMap<Long, OsmWay> ways;
    /**
     * Map of all relations.
     */
    private final HashMap<Long, OsmRelation> relations;
    /**
     * Constructs an new HighwayGraphBuilder.
     */
    public HighwayGraphBuilder() {
        this.nodes = new HashMap<Long, OsmNode>();
        this.ways = new HashMap<Long, OsmWay>();
        this.relations = new HashMap<Long, OsmRelation>();
    }
    /**
     * Builds the graph network from the given inputstream (osm xml).
     * @param inStream InputStream from xml data source
     * @param newTowns The Towns in the HighwayGraph
     * @param maxDistance The max distance between a Town and
     * a Highway to be connected
     * @return A HighwayGraph Network
     * @throws SAXException When an exception occurs
     * @throws IOException Source can't be opened
     * @throws ParserConfigurationException When an exception occurs
     */
    public HighwayGraph buildFromXml(final InputStream inStream,
            final Collection<OsmNode> newTowns, final long maxDistance)
            throws SAXException, IOException, ParserConfigurationException {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setNamespaceAware(true);
        SAXParser saxParser = spf.newSAXParser();
        XMLReader xmlReader = saxParser.getXMLReader();
        xmlReader.setContentHandler(new OsmSaxHandler(this));
        xmlReader.parse(new InputSource(inStream));
        System.out.println(this.nodes.size() + " nodes found.");
        System.out.println(this.ways.size() + " ways found.");
        System.out.println(this.relations.size() + " relations found.");
        OsmRelation germanHighways = this.relations.get(
                HighwayGraphBuilder.GERMANHIGHWAYSRELATIONID);
        LinkedList<OsmRelation> highwayRelations =
                new LinkedList<OsmRelation>();
        for (OsmRelationMember member : germanHighways.getMember()) {
            highwayRelations.add(
                    this.relations.get(member.getReferencedItem().getId()));
        }
        Map<OsmRelation, Collection<OsmNode>> highways =
                this.buildHighways(highwayRelations);
        HighwayGraphFactory factory = new HighwayGraphFactory();
        factory.setHighways(highways);
        factory.setTowns(newTowns);
        factory.setDistanceCalculator(new GeoDistanceCalculator());
        factory.setTownHighwayDistance(maxDistance);
        System.out.println(highways.size() + " highways loaded.");
        inStream.close();
        return factory.buildGraph();
    }

    private Map<OsmRelation, Collection<OsmNode>> buildHighways(
            final Collection<OsmRelation> highwayRelations) {
        HashMap<OsmRelation, Collection<OsmNode>> highways =
                new HashMap<OsmRelation, Collection<OsmNode>>(
                        highwayRelations.size());
        for (OsmRelation relation : highwayRelations) {
            OsmRelationNodeCollector collector =
                    new OsmRelationNodeCollector(relation, this.getProvider());
            highways.put(relation, collector.collectNodes());
        }
        return highways;
    }
    @Override
    public void nodeFound(final OsmNode node) {
        this.nodes.put(node.getId(), node);
    }

    @Override
    public void relationFound(final OsmRelation relation) {
        this.relations.put(relation.getId(), relation);
    }

    @Override
    public void wayFound(final OsmWay way) {
        this.ways.put(way.getId(), way);
    }

    @Override
    public OsmItemProvider getProvider() {
        return this;
    }
    @Override
    public boolean hasItem(final long id, final OsmXmlTypes type) {
       if (type.equals(OsmXmlTypes.Node)
               && this.nodes.containsKey(id)) {
           return true;
       }
       if (type.equals(OsmXmlTypes.Way)
               && this.ways.containsKey(id)) {
           return true;
       }
       if (type.equals(OsmXmlTypes.Relation)
               && this.relations.containsKey(id)) {
           return true;
       }
       System.err.println("No "
               + type.toString() + " with id " + id + " found.");
       return false;
    }
    @Override
    public OsmItem getItemById(final long id, final OsmXmlTypes type) {
        if (type.equals(OsmXmlTypes.Node) && this.nodes.containsKey(id)) {
            return this.nodes.get(id);
        }
        if (type.equals(OsmXmlTypes.Way) && this.ways.containsKey(id)) {
            return this.ways.get(id);
        }
        if (type.equals(OsmXmlTypes.Relation)
                && this.relations.containsKey(id)) {
            return this.relations.get(id);
        }
        throw new IllegalArgumentException("No "
                + type.toString() + " with id " + id + " found.");
    }
}
